package com.hjx.movie.core.system.service.user.impl;

import com.hjx.movie.common.utils.ResultType;
import com.hjx.movie.common.utils.exceptions.CustomException;
import com.hjx.movie.core.system.runner.SystemStaticData;
import com.hjx.movie.core.system.service.admin.*;
import com.hjx.movie.core.system.service.user.IUserMovieService;
import com.hjx.movie.core.system.vo.MovieShowItemVo;
import com.hjx.movie.core.system.vo.MovieShowVO;
import com.hjx.movie.core.system.vo.OrderSubVO;
import com.hjx.movie.core.system.vo.OrderVO;
import com.hjx.movie.core.util.cache.CacheKey;
import com.hjx.movie.core.util.cache.CacheUtil;
import com.hjx.movie.persistence.system.model.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author hjx
 * @date 2021/3/17 - 15:59
 */

@Service
@Transactional
@Slf4j
@CacheConfig(cacheNames = "IvyMovie:SpringCache:UserCache",keyGenerator = "cacheKeyGenerator")
public class UserMovieService implements IUserMovieService {
    @Resource
    IMovieService movieService;
    @Resource
    ICinemaService cinemaService;
    @Resource
    ICinemaHallService cinemaHallService;
    @Resource
    ICinemaMovieShowService cinemaMovieShow;
    @Resource
    ICinemaHallSeatService cinemaHallSeatService;
    @Resource
    ICinemaMovieShowService cinemaMovieShowService;
    @Resource
    IOrderService orderService;
    @Resource
    IOrderItemService orderItemService;
    @Resource
    SystemStaticData staticData;
    @Resource
    RedisTemplate<String,Object> redisTemplate;
    @Resource
    IAccountService accountService;
    //预占座位缓存名
    public static final String PRE_SELECT_SEAT = CacheUtil.cacheNameFormat(CacheKey.SYSTEM.getName(),"Seat","PreSelectSeat");
    //已占座位缓存名
    public static final String SELECTED_SEAT = CacheUtil.cacheNameFormat(CacheKey.SYSTEM.getName(),"Seat","SelectedSeat");
    //影厅全部座位
    public static final String HALL_ALL_SEAT = CacheUtil.cacheNameFormat(CacheKey.SYSTEM.getName(),"Seat","HallAllSeat");

    @Override
    public List<MovieShowVO> findMovieShowInfoList(Long cinemaId){
        //根据影院 查询有排片的影厅 并且排片的开始时间是在当前时间之后
        List<MovieShowVO> movieShowVOList = new ArrayList<>();
        List<IvyCinemaMovieShow> movieShowList = cinemaMovieShow.lambdaQuery()
                .eq(IvyCinemaMovieShow::getCinemaId, cinemaId)
                .ge(IvyCinemaMovieShow::getShowDateTime, LocalDateTime.now())
                .list().stream().sorted(Comparator.comparing(IvyCinemaMovieShow::getShowDateTime)).collect(Collectors.toList());
        if (!movieShowList.isEmpty()){
            //获取出有排片的电影ID
            Set<Long> mIdSet = movieShowList.stream().map(IvyCinemaMovieShow::getMovieId).collect(Collectors.toSet());
            Set<Long> mhIdSet = movieShowList.stream().map(IvyCinemaMovieShow::getCinemaHallId).collect(Collectors.toSet());
            //查询电影
            //List<IvyMovie>  movieList = movieService.lambdaQuery().in(IvyMovie::getMovieId, mIdSet).list();
            List<IvyMovie>  movieList = movieService.findMovieByIds(mIdSet);

            //查询影厅
            Map<Long,IvyCinemaHall> cinemaHallMap = cinemaHallService.lambdaQuery()
                    .in(IvyCinemaHall::getCinemaHallId, mhIdSet)
                    .list().stream().collect(Collectors.toMap(IvyCinemaHall::getCinemaHallId,item->item));
            //排片的遍历电影列表
            LocalDate today = LocalDate.now();
            LocalDate nextDay = today.plus(1, ChronoUnit.DAYS);
            for(IvyMovie item:movieList){
                //影厅项
                List<MovieShowItemVo> todayMovieShowItemVoList = new ArrayList<>();
                List<MovieShowItemVo> nextDayMovieShowItemVoList = new ArrayList<>();
                movieShowList.forEach(ms->{
                    if (item.getMovieId().equals(ms.getMovieId())){
                        //获取影厅名
                        String cinemaHallName = cinemaHallMap.get(ms.getCinemaHallId()).getCinemaHallName();
                        if (today.equals(LocalDate.parse(ms.getShowDate(), DateTimeFormatter.ISO_DATE))){
                            // 如果排片日期是今天 就把排片加入到今天的列表中
                            todayMovieShowItemVoList.add(new MovieShowItemVo(cinemaHallName,ms));
                        }else if (nextDay.equals(LocalDate.parse(ms.getShowDate(), DateTimeFormatter.ISO_DATE))){
                            //加入明天的列表中
                            nextDayMovieShowItemVoList.add(new MovieShowItemVo(cinemaHallName,ms));
                        }
                    }
                });
                Map<String,List<MovieShowItemVo>> map = new HashMap<>();
                map.put("today",todayMovieShowItemVoList);
                map.put("nextDay",nextDayMovieShowItemVoList);
                //组装数据
                MovieShowVO movieShowVO = MovieShowVO.builder().movieId(item.getMovieId())
                        .movieName(item.getMovieName()).actor(item.getActor()).time(item.getTime())
                        .picture(item.getPicture()).rate(item.getRate()).language(item.getLanguage()).movieShowMap(map)
                        .build();
                movieShowVOList.add(movieShowVO);
            }
        }
        return movieShowVOList;
    }
    // 选座 查看座位信息
    @Override
    public Map<String,Object> findSeatInfo(Long msid){
        HashOperations<String ,String,List<Long>> seatCache = redisTemplate.opsForHash();

        LocalDate preDay = LocalDate.now().minus(1, ChronoUnit.DAYS);
        Map<String,Object> map = new HashMap<>();
        IvyCinemaMovieShow movieShow = cinemaMovieShowService.findMovieShowById(msid);
        log.debug("查询影院信息");
        map.put("cinemaInfo",cinemaService.findCinemaById(movieShow.getCinemaId()));
        log.debug("查询影厅信息");
        IvyCinemaHall hall = cinemaHallService.findCinemaHallById(movieShow.getCinemaHallId());
        map.put("hallInfo",hall);
        log.debug("查询电影信息");
        map.put("movieInfo",movieService.findMovieById(movieShow.getMovieId()));
        log.debug("查询电影放映场次信息");
        map.put("movieShowInfo", movieShow);
        log.debug("查询影厅座位信息");
        // 将座位的数据的结构由
        List<IvyCinemaHallSeat> hallSeat = cinemaHallSeatService.findByCinemaId(movieShow.getCinemaHallId());
        Map<String,List<IvyCinemaHallSeat>> hallSeatInfo = new HashMap<>();
        for(int y=1;y<=hall.getMaxSeatY();y++){
            int index = y;
            // 过滤出行为Y的数据 并且根据列X进行排序
            List<IvyCinemaHallSeat> seatList= hallSeat.stream()
                    .filter(item -> item.getSeatY().equals(index))
                    .sorted(Comparator.comparing(IvyCinemaHallSeat::getSeatX))
                    .collect(Collectors.toList());

            hallSeatInfo.put(String.valueOf(y),seatList);
        }
        // 将这个影厅的全部座位ID存入缓存
        List<Long> longs = seatCache.get(HALL_ALL_SEAT, hall.getCinemaHallId().toString());
        if (longs==null){
            List<Long> seatIds = hallSeat.stream().map(IvyCinemaHallSeat::getSeatId).collect(Collectors.toList());
            seatCache.put(HALL_ALL_SEAT, hall.getCinemaHallId().toString(), seatIds);
        }
        map.put("hallSeatInfo", hallSeatInfo);

        List<Long> notSelectSeatIds = new ArrayList<>();
        //获取缓存中已选座位列表
        List<Long> cacheSelectSeatIds = seatCache.get(SELECTED_SEAT,msid.toString());
        if (cacheSelectSeatIds==null||cacheSelectSeatIds.isEmpty()){
            // 根据放映场次ID查询订单 然后根据订单查询订单项 获取到已被选择的座位
            List<Long> orderIds = orderService.lambdaQuery()
                    .eq(IvyOrder::getMovieShowId, msid)
                    .eq(IvyOrder::getStatus, Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                    .ge(IvyOrder::getCreateTime, preDay.toString() + " 00:00:00").list()
                    .stream().map(IvyOrder::getOrderId).collect(Collectors.toList());
            //如果订单不为空 则去查询订单项 获取已被选择的座位信息
            if (!orderIds.isEmpty()){
                notSelectSeatIds = orderItemService.lambdaQuery()
                        .in(IvyOrderItem::getOrderId,orderIds).list()
                        .stream().map(IvyOrderItem::getSeatId).collect(Collectors.toList());
            }
            //如果查出来的结果不是空的 则同步进缓存
            if (!notSelectSeatIds.isEmpty()){
                seatCache.put(SELECTED_SEAT,msid.toString(),notSelectSeatIds);
            }
        }else {
            log.debug("缓存中存在已选座位信息===>{}",cacheSelectSeatIds);
            notSelectSeatIds = cacheSelectSeatIds;
        }
        map.put("notSelectSeatIds",notSelectSeatIds);
        return map;
    }

    //提交订单前的选座检查
    @Override
    public Map<String,Object> preOrderCheck(OrderSubVO params){
        Map<String,Object> resultMap = new HashMap<>();
        HashOperations<String ,String,List<Long>> seatCache = redisTemplate.opsForHash();
        // 检查座位的状态 是不是已经被选
        LocalDate preDay = LocalDate.now().minus(1, ChronoUnit.DAYS);
        List<Long> hallSeatIds;
        List<Long> notSelectSeatIds = new ArrayList<>();
        List<Long> preSelectSeatIds;
        Assert.notNull(params.getMovieShowId(),"放映场次ID不许为空");
        Assert.notEmpty(params.getSeatIds(),"选择的座位不许为空");

        List<Long> cacheSelectSeatIds = seatCache.get(SELECTED_SEAT,params.getMovieShowId().toString());
        if (cacheSelectSeatIds==null){
            // 根据放映场次ID查询订单 然后根据订单查询订单项 获取到已被选择的座位
            List<Long> orderIds = orderService.lambdaQuery()
                    .eq(IvyOrder::getMovieShowId, params.getMovieShowId())
                    .eq(IvyOrder::getStatus, Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                    .ge(IvyOrder::getCreateTime, preDay.toString() + " 00:00:00").list()
                    .stream().map(IvyOrder::getOrderId).collect(Collectors.toList());

            if (!orderIds.isEmpty()){
                // 根据订单查询订单项 获取到已被选择的座位
                notSelectSeatIds = orderItemService.list(orderItemService.lambdaQuery()
                        .in(IvyOrderItem::getOrderId,orderIds)
                ).stream().map(IvyOrderItem::getSeatId).collect(Collectors.toList());
            }
            //如果查出来的结果不是空的 则同步进缓存
            if (!notSelectSeatIds.isEmpty()){
                seatCache.put(SELECTED_SEAT,params.getMovieShowId().toString(),notSelectSeatIds);
            }
        }else {
            log.debug("缓存中存在已选座位信息===>{}",cacheSelectSeatIds);
            notSelectSeatIds = cacheSelectSeatIds;
        }
        hallSeatIds = seatCache.get(HALL_ALL_SEAT,params.getHallId().toString());
        Assert.notNull(hallSeatIds,"放映厅座位不能为空");
        log.debug("获取影厅的所有的座位ID===>{}",hallSeatIds);
        preSelectSeatIds = seatCache.get(PRE_SELECT_SEAT,params.getMovieShowId().toString())==null?
                new ArrayList<>():seatCache.get(PRE_SELECT_SEAT,params.getMovieShowId().toString());
        log.debug("获取已被预占的座位ID===>{}",preSelectSeatIds);
        // 预占的逻辑还需要完善
//        if (!preSelectSeatIds.isEmpty()){
//            //移除已经被预占的座位
//            hallSeatIds.removeAll(preSelectSeatIds);
//        }

        //移除已选的座位
        hallSeatIds.removeAll(notSelectSeatIds);
        log.debug("获取出可选的座位ID===>{}",hallSeatIds);
        log.debug("判断选择的座位在不在可选的座位之内===>{}",hallSeatIds.containsAll(params.getSeatIds()));
        if (hallSeatIds.containsAll(params.getSeatIds())){
            //将座位加入到预选座位列表中
            preSelectSeatIds.addAll(params.getSeatIds());
            seatCache.put(PRE_SELECT_SEAT,params.getMovieShowId().toString(),preSelectSeatIds);
            //组装返回的信息
            log.debug("查询影厅座位信息");
            List<IvyCinemaHallSeat> ivyCinemaHallSeats = cinemaHallSeatService.listByIds(params.getSeatIds())
                    .stream().sorted(Comparator.comparing(IvyCinemaHallSeat::getSeatX)).collect(Collectors.toList());
            IvyAccount account = accountService.getById(params.getAccountId());
            resultMap.put("selectSeatInfo",ivyCinemaHallSeats);
            resultMap.put("accountInfo",account);
            IvyCinemaMovieShow movieShow = cinemaMovieShowService.findMovieShowById(params.getMovieShowId());
            BigDecimal multiply = movieShow.getOldPrice().multiply(BigDecimal.valueOf(params.getSeatIds().size()));
            log.debug("计算出的总价为:{}",multiply.toString());
            resultMap.put("totalPrice",multiply);
            return resultMap;
        }else {
            throw new CustomException(ResultType.USER_INPUT_ERROR,"预占座位失败，请重新选择");
        }
    }
    //新增订单
    @Override
    public Boolean addOrder(OrderSubVO params){
        IvyCinemaMovieShow movieShow = cinemaMovieShowService.findMovieShowById(params.getMovieShowId());
        BigDecimal totalPrice = movieShow.getOldPrice().multiply(BigDecimal.valueOf(params.getSeatIds().size()));
        BigDecimal newTotalPrice = movieShow.getNewPrice().multiply(BigDecimal.valueOf(params.getSeatIds().size()));
        // 使用帐号ID查询余额
        IvyAccount account = accountService.getById(params.getAccountId());
        int i = account.getBalance().compareTo(totalPrice);
        if (i < 0){
            throw new CustomException(ResultType.USER_INPUT_ERROR,"账户余额不足");
        }else {
            BigDecimal subtract = account.getBalance().subtract(totalPrice);
            account.setBalance(subtract);
        }
        DateTimeFormatter  dt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
        String sn = LocalDateTime.now().format(dt)+ System.currentTimeMillis();
        // 扣钱 创建订单和订单项 保存
        IvyOrder order = IvyOrder.builder()
                .accountId(params.getAccountId()).movieShowId(params.getMovieShowId())
                .oldPrice(totalPrice).newPrice(newTotalPrice)
                .status(Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                .num(params.getSeatIds().size()).sn(sn)
                .build();

        if (accountService.updateById(account)){
            if (orderService.addOrder(order)){
                List<IvyOrderItem> orderItemList = new ArrayList<>();
                for (Long id:params.getSeatIds()){
                    orderItemList.add(IvyOrderItem.builder()
                            .orderId(order.getOrderId()).realPrice(movieShow.getOldPrice()).seatId(id)
                            .status(Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                            .build());
                }
                // 检查
                if(orderItemService.saveBatch(orderItemList)){
                    return true;
                } else throw new CustomException(ResultType.USER_INPUT_ERROR,"创建订单项失败");
            }else throw new CustomException(ResultType.USER_INPUT_ERROR,"创建订单失败");
        }else throw new CustomException(ResultType.USER_INPUT_ERROR,"支付失败");
    }
    @Override
    public List<OrderVO> findOrderByAccountId(Long aid){
        // 帐号查询订单
        List<IvyOrder> orders = orderService.lambdaQuery()
                .eq(IvyOrder::getAccountId, aid)
                .eq(IvyOrder::getStatus, Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                .list();
        List<OrderVO> orderVOList = new ArrayList<>();
        for (IvyOrder order:orders){
            IvyCinemaMovieShow movieShow = cinemaMovieShowService.findMovieShowById(order.getMovieShowId());
            IvyMovie movie = movieService.findMovieById(movieShow.getMovieId());
            IvyCinemaHall cinemaHall = cinemaHallService.findCinemaHallById(movieShow.getCinemaHallId());
            IvyCinema cinema = cinemaService.findCinemaById(movieShow.getCinemaId());
            List<Long> seatIds = orderItemService.lambdaQuery().eq(IvyOrderItem::getOrderId, order.getOrderId()).list()
                    .stream().map(IvyOrderItem::getSeatId).collect(Collectors.toList());
            // 用订单项找座位号
            List<String> seats = cinemaHallSeatService.listByIds(seatIds).stream().map(item->item.getSeatY()+"排"+item.getSeatX()+"座").collect(Collectors.toList());
            OrderVO orderVO = new OrderVO(cinema.getCinemaName(), cinemaHall.getCinemaHallName(),
                    movie.getMovieName(),movie.getPicture(),seats,order);
            orderVOList.add(orderVO);
        }
        return orderVOList;
    }
    @Override
    public List<IvyOrderItem> findOrderItemByOrderId(Long oid){
        // 订单查询订单项
        return orderItemService.lambdaQuery()
                .eq(IvyOrderItem::getOrderId, oid)
                .eq(IvyOrderItem::getStatus, Integer.parseInt(staticData.getStaticValue("ORDER_NORMAL")))
                .list();
    }

}
